/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package pe.edu.pucp.dp1.procesamiento.algoritmo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import pe.edu.pucp.dp1.persistencia.bd.BaseDatos;
import pe.edu.pucp.dp1.procesamiento.controlador.GestorInterseccion;
import static pe.edu.pucp.dp1.procesamiento.controlador.GestorInterseccion.tablaConfig;
import pe.edu.pucp.dp1.procesamiento.modelo.InterseccionEntrada;
import pe.edu.pucp.dp1.procesamiento.modelo.InterseccionOptima;
import pe.edu.pucp.dp1.procesamiento.modelo.MatrizInterseccionEntrada;
import pe.edu.pucp.dp1.procesamiento.modelo.MatrizInterseccionOptima;

/**
 *
 * @author Edgard
 */
public class Genetico {

    static final int LARGO_ZONA = 10;
    static final int NUM_CONFIG = 7;
    static final int NUM_IND_POB = 10;//Numero de individuos por poblacion
    static final int NUM_GEN = 1;//Numero de generaciones

    private MatrizInterseccionEntrada matrizIntEntrada;
    private MatrizInterseccionOptima matrizIntOptima;

    public static GestorInterseccion gestorInter = GestorInterseccion.getInstance();

    //private BaseDatos bd;
    public Genetico(MatrizInterseccionEntrada matrizIntEntrada) {
        this.matrizIntEntrada = matrizIntEntrada;
    }

    public Genetico() {

    }

    public void GenSolOptima(int iniX, int iniY) {

        //MatrizInterseccionEntrada matrizIntEntrada = gestorInter.getMatrizTotalEntrada()[iniX][iniY];
        //MatrizInterseccionOptima matrizIntOptima = gestorInter.getMatrizTotalOptima()[iniX][iniY];
        Individuo indElite = new Individuo();

        ArrayList<Individuo> pobInicial = GenPoblacionInicial(iniX, iniY, NUM_IND_POB);

        //Tomamos al primer elemento como IndElite, solo para inicializarlo
        //System.out.println("Size pobInicial: " + pobInicial.size());
        indElite = pobInicial.get(0);

        //Elitismo - Para guardar la mejor solucion
        //Reproduccion - Se duplica el num de individos cruzandolos
        //Seleccion - Se selecciona a los mejores
        //Mutacion - Se modifica partes de los genes de un individuo
        //Aplicamos operaciones de algoritmo genetico
        //Reproduccion - Pob*2
        //Mutacion - Pob*4
        //Seleccion - Elitismo - Dividimos Pob/4 y guardamos el mejor adaptado
        for (int i = 0; i < NUM_GEN; i++) {
            //System.out.println("Iteracion Gen: " + i);
            //REPRODUCCION
            ArrayList<Individuo> pobRep = geneticoReproduccion(pobInicial);
            //MUTACION
            ArrayList<Individuo> pobMut = geneticoMutacion(pobRep);
            //SELECCION - Evaluacion FO
            ArrayList<Individuo> pobSel = geneticoSeleccion(iniX, iniY, pobMut, indElite);

            pobInicial = pobSel;
        }
        genMatrizIntOptima(iniX, iniY, indElite);
        //System.out.println("Fin GenSolOptima");
    }

    /* Edgard: Funcion que genera una poblacion inicial como solucion aleatoria
     numInd es el numero de individuos que conforman la poblacion inicial
     */
    ArrayList<Individuo> GenPoblacionInicial(int iniX, int iniY, int numInd) {
        ArrayList<Individuo> auxPobInicial = new ArrayList<Individuo>();
        for (int i = 0; i < numInd; i++) {
            Individuo ind = GenIndAleatorio(iniX, iniY);
            auxPobInicial.add(ind);
        }
        return auxPobInicial;
    }
    /* Edgard: Generar Individuo Aleatorio */

    Individuo GenIndAleatorio(int iniX, int iniY) {

        MatrizInterseccionEntrada matrizIntEntrada = gestorInter.getMatrizTotalEntrada()[iniX][iniY];

        Individuo auxInd = new Individuo();
        ArrayList<Gen> genes = new ArrayList<>();

        for (int i = 0; i < LARGO_ZONA; i++) {
            for (int j = 0; j < LARGO_ZONA; j++) {
                if (matrizIntEntrada.getMatrizInterseccionEntrada()[i][j] != null) {
                    /* Gen aleatorio */
                    Gen gen = new Gen((int) (Math.random() * 7));
                    genes.add(gen);
                }
            }
        }
        auxInd.setGenes(genes);
        return auxInd;
    }

    /* Edgard: Generar MatrizIntOptima a partir del Individuo de Elite
     */
    void genMatrizIntOptima(int iniX, int iniY, Individuo indElite) {
        MatrizInterseccionEntrada matrizIntEntrada = gestorInter.getMatrizTotalEntrada()[iniX][iniY];
        MatrizInterseccionOptima matrizIntOptima = gestorInter.getMatrizTotalOptima()[iniX][iniY];

        int auxPos = 0;
        for (int i = 0; i < LARGO_ZONA; i++) {
            for (int j = 0; j < LARGO_ZONA; j++) {
                if (matrizIntEntrada.getMatrizInterseccionEntrada()[i][j] != null) {
                    int auxConf = indElite.getGenes().get(auxPos).getConfiguracion();
                    auxPos++;
                    matrizIntOptima.getMatrizInterseccionOptima()[i][j].setConfiguracion(auxConf);
                }
            }
        }
    }

    ArrayList<Individuo> geneticoReproduccion(ArrayList<Individuo> pobInicial) {
        //ArrayList<Individuo> pobRep = new ArrayList<Individuo>();
/*
         for (int i = 0; i < pobInicial.size(); i++) {
         Individuo auxInd1 = new Individuo();
         Individuo auxInd2 = new Individuo();
         ArrayList<Gen> auxGen1 = new ArrayList<Gen>();
         ArrayList<Gen> auxGen2 = new ArrayList<Gen>();

         ArrayList<Gen> auxGenIni1 = pobInicial.get(i).getGenes();
         ArrayList<Gen> auxGenIni2 = pobInicial.get(i + 1).getGenes();

         int auxSize = auxGen1.size();
         int auxSizeDiv2 = auxGen1.size() / 2;
         Gen auxGen;

         if (auxSizeDiv2 > 0) {
         for (int j = 0; j < auxSizeDiv2; j++) {
         auxGen=new Gen(auxGenIni1.get(i).getConfiguracion());
         auxGen1.add(auxGen);
         auxGen=new Gen(auxGenIni2.get(i).getConfiguracion());
         auxGen2.add(auxGen);
         }
         for (int j = auxSizeDiv2; j < auxSize; j++) {
         auxGen=new Gen(auxGenIni1.get(j).getConfiguracion());
         auxGen2.add(auxGen);
         auxGen=new Gen(auxGenIni2.get(j).getConfiguracion());
         auxGen1.add(auxGen);
         }
         auxInd1.setGenes(auxGen1);
         auxInd2.setGenes(auxGen2);
         }
         }*/
        return pobInicial;
    }

    ArrayList<Individuo> geneticoMutacion(ArrayList<Individuo> pobRep) {
        return pobRep;
    }

    ArrayList<Individuo> geneticoSeleccion(int iniX, int iniY, ArrayList<Individuo> pobMut, Individuo indElite) {

        //System.out.println("Size pobMut: " + pobMut.size());

        ArrayList<valIndividuo> pobMutVal = new ArrayList<valIndividuo>();
        int z;
        valIndividuo valInd;
        for (int i = 0; i < pobMut.size(); i++) {
            z = funObjetivo(iniX, iniY, pobMut.get(i));
            //System.out.println("z: " + z);
            valInd = new valIndividuo();
            valInd.setValor(z);
            valInd.setIndividuo(ClonarIndividuo(pobMut.get(i)));
            pobMutVal.add(valInd);
        }
        //Edgard: ordenamiento
        Collections.sort(pobMutVal, new Comparator<valIndividuo>() {
            @Override
            public int compare(valIndividuo o1, valIndividuo o2) {
                int rpta = 0;
                if (o1.getValor() == o2.getValor()) {
                    rpta = 0;
                } else {
                    if (o1.getValor() < o2.getValor()) {
                        rpta = -1;
                    } else {
                        rpta = +1;
                    };
                }
                return rpta;
            }
        });
        //Despues de Ordenado, sacamos el Individuo optimo
        Individuo auxIndOptimo = pobMutVal.get(pobMutVal.size() - 1).getIndividuo();
        for (int i = 0; i < auxIndOptimo.getGenes().size(); i++) {
            Gen auxGen = indElite.getGenes().get(i);
            auxGen.setConfiguracion(auxIndOptimo.getGenes().get(i).getConfiguracion());
        }
        System.out.println("FO Zona: " + funObjetivo(iniX, iniY, auxIndOptimo));

        //Liberar Memoria de pobMut
        return pobMut;
    }

    private int funObjetivo(int iniX, int iniY, Individuo ind) {

        MatrizInterseccionEntrada auxMatrizIntEntrada = gestorInter.getMatrizTotalEntrada()[iniX][iniY];

        int auxPos = 0;
        int totalFO = 0;
        for (int i = 0; i < LARGO_ZONA; i++) {
            for (int j = 0; j < LARGO_ZONA; j++) {
                if (auxMatrizIntEntrada.getMatrizInterseccionEntrada()[i][j] != null) {
                    //Limite Velocidad
                    //Cant Vehiculos
                    int auxConfig = ind.getGenes().get(auxPos).getConfiguracion();

                    auxPos++;

                    InterseccionEntrada auxInterEntrada = auxMatrizIntEntrada.getMatrizInterseccionEntrada()[i][j];

                    //System.out.println("Vehic: "+auxInterEntrada.getnAutosEste());
                    //System.out.println("Vehic: "+auxInterEntrada.getnAutosNorte());
                    //Distancia recorrida = velocidadLimiteHor * tiempoTotalHor * numVehiculosHor
                    int auxDurTiempoVerHor = tablaConfig.ObtenerConfiguracion(auxConfig).getTiempoVerdeHor();
                    int auxDurTiempoVerVer = tablaConfig.ObtenerConfiguracion(auxConfig).getTiempoVerdeVer();

                    int velX = 40;
                    int velY = 40;
                    //Obtenemos velocidades de avenida
                    try {
                        BaseDatos bd = new BaseDatos();
                        velX = bd.GetVelocidadAvenida("H", auxMatrizIntEntrada.getxIni() + j * 100);
                        velY = bd.GetVelocidadAvenida("V", auxMatrizIntEntrada.getyIni() + i * 100);
                    } catch (Exception e) {
                        System.out.println("No se puede leer velocidad X: " + (auxMatrizIntEntrada.getxIni() + j * 100));
                        System.out.println("No se puede leer velocidad Y: " + (auxMatrizIntEntrada.getyIni() + i * 100));
                    }
//                    System.out.println("i j: " + i + " " + j);
//                    System.out.println("auxDurTiempoVerHor: " + auxDurTiempoVerHor);
//                    System.out.println("auxDurTiempoVerVer: " + auxDurTiempoVerVer);
//                    System.out.println("velX: " + velX);
//                    System.out.println("velY: " + velY);
                    int zHor = (auxInterEntrada.getnAutosEste() + auxInterEntrada.getnAutosOeste()) * auxDurTiempoVerHor * velX;
                    int zVer = (auxInterEntrada.getnAutosSur() + auxInterEntrada.getnAutosNorte()) * auxDurTiempoVerVer * velY;

                    totalFO += zHor + zVer;
                }
            }
        }
        //System.out.println("Fin FuncionObjetivo");
        //System.out.println("psedoFO: "+pseduoFO);
        return totalFO;
    }

    public static Individuo ClonarIndividuo(Individuo indOriginal) {
        int numGenes = indOriginal.getGenes().size();
        Individuo indClon = new Individuo();
        ArrayList<Gen> genes = new ArrayList<Gen>();
        for (int i = 0; i < numGenes; i++) {
            Gen genNuevo = new Gen();
            genNuevo.setConfiguracion(indOriginal.getGenes().get(i).getConfiguracion());
            genes.add(genNuevo);
        }
        indClon.setGenes(genes);
        return indClon;
    }
}
